home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1998…tember: Reference Library / Dev.CD Sep 98 RL1.toast / Technical Documentation / develop / develop Issue 24 / develop Issue 24 code / Scriptable Database 1.0a15.sea / Scriptable Database 1.0a15 / Database / DatabaseDocument.cp / DatabaseDocument.cp
Encoding:
Text File  |  1996-04-29  |  37.8 KB  |  1,104 lines  |  [TEXT/CWIE]

  1. //================================================================================
  2. // Greg Anderson
  3. // db+
  4. //
  5. // Abstract base class for DataBase document
  6. // 17 May 1994
  7. // 31 Dec 1994
  8. //================================================================================
  9.  
  10. #include "DatabaseDocument.h"
  11.  
  12. #include "GroupControlObject.h"
  13. #include "AbstractRecord.h"
  14. #include "DBElement.h"
  15. #include "DBProperty.h"
  16. #include "DataRecord.h"
  17. #include "Transaction.h"
  18. #include "UniqueID.h"
  19.  
  20. #include "Exceptions.h"
  21.  
  22. #define kFreeRecordListIndex 0
  23. #define kLastDataBlockFreeList (kNumberFreeLists - 1)
  24.  
  25. //
  26. // For memcpy
  27. // For CopyMemory
  28. //
  29. #include "AbstractData.h"
  30.  
  31. //--------------------------------------------------------------------------------
  32. // TDocumentFileInformation::AssignInitialID
  33. //--------------------------------------------------------------------------------
  34. void TDocumentFileInformation::AssignInitialID()
  35. {
  36.     fDocumentID = GenerateUniqueID();
  37. }
  38.  
  39. //--------------------------------------------------------------------------------
  40. // TDatabaseDocument::~TDatabaseDocument
  41. //--------------------------------------------------------------------------------
  42. TDatabaseDocument::~TDatabaseDocument()
  43. {
  44.     //
  45.     // Save changes back to disk before deleting ourselves
  46.     //
  47.     // Shouldn't do this in the destructor; we should
  48.     // make a virtual 'Dispose' method, and keep the
  49.     // destructor protected.
  50.     //
  51.     this->FlushChangesToDisk();
  52.     
  53.     //
  54.     // The database document owns the backing store object,
  55.     // and deletes it at destructor time.
  56.     //
  57.     delete fBackingStore;
  58.     fBackingStore = nil;
  59. } // TDatabaseDocument::~TDatabaseDocument
  60.  
  61. //--------------------------------------------------------------------------------
  62. // TDatabaseDocument::ObjectsKeySpace
  63. //--------------------------------------------------------------------------------
  64. Int64 TDatabaseDocument::ObjectsKeySpace() const
  65. {
  66.     return fDocumentInformation.fDocumentID;
  67. } // TDatabaseDocument::ObjectsKeySpace
  68.     
  69. //--------------------------------------------------------------------------------
  70. // TDatabaseDocument::ReadRecordRange
  71. //--------------------------------------------------------------------------------
  72. void TDatabaseDocument::ReadRecordRange(void* bufferStart, long byteOffsetToRecord, long numberOfBytes, TAbstractBackingStore* backingStoreToUse /*= nil*/)
  73. {
  74.     REQUIREVALIDPOINTER(bufferStart);
  75.     if(backingStoreToUse == nil)
  76.         backingStoreToUse = fBackingStore;
  77.     
  78.     if(backingStoreToUse != nil)
  79.         backingStoreToUse->Read(bufferStart, byteOffsetToRecord + this->fDocumentInformation.fStartOfRecordData, numberOfBytes);
  80. } // TDatabaseDocument::ReadRecordRange
  81.  
  82. //--------------------------------------------------------------------------------
  83. // TDatabaseDocument::WriteRecordRange
  84. //--------------------------------------------------------------------------------
  85. void TDatabaseDocument::WriteRecordRange(void* bufferStart, long byteOffsetToRecord, long numberOfBytes, TAbstractBackingStore* backingStoreToUse /*= nil*/)
  86. {
  87.     REQUIREVALIDPOINTER(bufferStart);
  88.     if(backingStoreToUse == nil)
  89.         backingStoreToUse = fBackingStore;
  90.  
  91.     if(backingStoreToUse != nil)
  92.         backingStoreToUse->Write(bufferStart, byteOffsetToRecord + this->fDocumentInformation.fStartOfRecordData, numberOfBytes);
  93. } // TDatabaseDocument::WriteRecordRange
  94.  
  95. //--------------------------------------------------------------------------------
  96. // TDatabaseDocument::GetFirstFreeIndex
  97. //--------------------------------------------------------------------------------
  98. long TDatabaseDocument::GetFirstFreeIndex(long whichFreeList) const
  99. {
  100.     Require((whichFreeList >= 0) && (whichFreeList < kNumberFreeLists));
  101.     return fFreeList[whichFreeList];
  102. } // TDatabaseDocument::GetFirstFreeIndex
  103.  
  104. //--------------------------------------------------------------------------------
  105. // TDatabaseDocument::SetFreeIndex
  106. //--------------------------------------------------------------------------------
  107. void TDatabaseDocument::SetFreeIndex(long whichFreeList, long firstFree)
  108. {
  109.     Require((whichFreeList >= 0) && (whichFreeList < kNumberFreeLists));
  110.     fFreeList[whichFreeList] = firstFree;
  111. } // TDatabaseDocument::SetFreeIndex
  112.  
  113. //--------------------------------------------------------------------------------
  114. // TDatabaseDocument::GetRecordCursor
  115. //--------------------------------------------------------------------------------
  116. AConst<TAbstractRecord> TDatabaseDocument::GetRecordCursor(long recordIndex) const
  117. {
  118.     return AConst<TAbstractRecord>(this->GetRecord(recordIndex));
  119. } // TDatabaseDocument::GetRecordCursor
  120.  
  121. //--------------------------------------------------------------------------------
  122. // TDatabaseDocument::GetRecord
  123. //--------------------------------------------------------------------------------
  124. TAbstractRecord* TDatabaseDocument::GetRecord(long recordIndex) const
  125. {
  126.     TAbstractRecord* cursor = nil;
  127.     
  128.     if(recordIndex != kNilIndex)
  129.     {
  130.         TGroupControlObject* group = this->GetGroupControlObject(recordIndex);
  131.         cursor = group->GetRecord(recordIndex);
  132.     }
  133.     
  134.     return cursor;
  135. } // TDatabaseDocument::GetRecord
  136.  
  137. //--------------------------------------------------------------------------------
  138. // TDatabaseDocument::FlushChangesToDisk
  139. //
  140. // Write everything in the database back to disk
  141. //--------------------------------------------------------------------------------
  142. void TDatabaseDocument::FlushChangesToDisk()
  143. {
  144.     //
  145.     // Write our document information back to disk
  146.     //
  147.     this->WriteDocumentInformation();
  148.     
  149.     //
  150.     // For every cached group control object we have
  151.     // in memory, call its flush changes method.
  152.     //
  153.     if(fRecordGroupList != nil)
  154.         for(long i=0; i<fNumberOfGroups; ++i)
  155.             if(fRecordGroupList[i] != nil)
  156.                 fRecordGroupList[i]->FlushChangesToDisk();
  157. }
  158.  
  159. //--------------------------------------------------------------------------------
  160. // TDatabaseDocument::SetBackingStore
  161. //--------------------------------------------------------------------------------
  162. void TDatabaseDocument::SetBackingStore(TAbstractBackingStore* backingStore)
  163. {
  164.     TAbstractBackingStore* previousBackingStore = fBackingStore;
  165.     
  166.     //
  167.     // If we already have a backing store, make sure that
  168.     // the file it is attached to is saved before the backing
  169.     // store object is deleted
  170.     //
  171.     this->FlushChangesToDisk();
  172.     
  173.     //
  174.     // Set the new backing store object
  175.     //
  176.     fBackingStore = backingStore;
  177.  
  178.     //
  179.     // Begin by writing out the document information into the new
  180.     // backing store object.
  181.     //
  182.     this->WriteDocumentInformation();
  183.     
  184.     //
  185.     // There may be chunks of the database still on disk in
  186.     // the previous backing store object.  We must load them into
  187.     // memory and save them back to the new backing store object.
  188.     //
  189.     if(fRecordGroupList != nil)
  190.     {
  191.         for(long i=0; i<fNumberOfGroups; ++i)
  192.         {
  193.             //
  194.             // If the record group is not in memory, then we
  195.             // must read it in from the previous backing store
  196.             // object.
  197.             //
  198.             if((fRecordGroupList[i] == nil) && (previousBackingStore != nil))
  199.             {
  200.                 TGroupControlObject* group = CreateGroupControlObject(i * kRecordsPerGroup);
  201.                 ASSERT(group == fRecordGroupList[i]);
  202.                 group->ReadRecordGroupFromDisk(previousBackingStore);
  203.             }
  204.             //
  205.             // If we have the record group in memory, then write
  206.             // it out to the new backing store object.
  207.             //
  208.             if(fRecordGroupList[i] != nil)
  209.             {
  210.                 fRecordGroupList[i]->RecordGroupHasChanged();
  211.                 fRecordGroupList[i]->FlushChangesToDisk();
  212.             }
  213.         }
  214.     }
  215.     
  216.     //
  217.     // Get rid of the old backing store object now that it is no
  218.     // longer needed for anything
  219.     //
  220.     delete previousBackingStore;
  221. } // TDatabaseDocument::SetBackingStore
  222.  
  223. //--------------------------------------------------------------------------------
  224. // TDatabaseDocument::SaveACopy
  225. //--------------------------------------------------------------------------------
  226. void TDatabaseDocument::SaveACopy(TAbstractBackingStore* backingStore)
  227. {
  228.     //
  229.     // If we already have a backing store, make sure that
  230.     // the file it is attached to is saved before we
  231.     // save a copy of it.
  232.     //
  233.     this->FlushChangesToDisk();
  234.         
  235.     //
  236.     // Begin by writing out the document information into the new
  237.     // backing store object.
  238.     //
  239.     this->WriteDocumentInformation(backingStore);
  240.     
  241.     //
  242.     // There may be chunks of the database paged out to disk;
  243.     // We must load them into memory and save them back to the
  244.     // new backing store.
  245.     //
  246.     if(fRecordGroupList != nil)
  247.     {
  248.         for(long i=0; i<fNumberOfGroups; ++i)
  249.         {
  250.             //
  251.             // If the record group is not in memory, then we
  252.             // must read it in from the previous backing store
  253.             // object.
  254.             //
  255.             if(fRecordGroupList[i] == nil)
  256.             {
  257.                 TGroupControlObject* group = CreateGroupControlObject(i * kRecordsPerGroup);
  258.                 ASSERT(group == fRecordGroupList[i]);
  259.                 group->ReadRecordGroupFromDisk();
  260.             }
  261.             //
  262.             // If we have the record group in memory, then write
  263.             // it out to the new backing store object.
  264.             //
  265.             if(fRecordGroupList[i] != nil)
  266.             {
  267.                 fRecordGroupList[i]->RecordGroupHasChanged();
  268.                 fRecordGroupList[i]->FlushChangesToDisk(backingStore);
  269.             }
  270.         }
  271.     }
  272. } // TDatabaseDocument::SaveACopy
  273.  
  274. //--------------------------------------------------------------------------------
  275. // TDatabaseDocument::CanSaveDocument
  276. //--------------------------------------------------------------------------------
  277. Boolean TDatabaseDocument::CanSaveDocument()
  278. {
  279.     Boolean canSave = false;
  280.     
  281.     if(fBackingStore != nil)
  282.         canSave = fBackingStore->CanSaveDocument();
  283.         
  284.     return canSave;
  285. } // TDatabaseDocument::CanSaveDocument
  286.  
  287. //--------------------------------------------------------------------------------
  288. // TDatabaseDocument::DocumentName
  289. //--------------------------------------------------------------------------------
  290. void TDatabaseDocument::DocumentName(TUpdataDataReference& name)
  291. {
  292.     if(fBackingStore != nil)
  293.         fBackingStore->DocumentName(name);
  294.     else
  295.         name.SetDataLength(0);
  296. }
  297.  
  298. //--------------------------------------------------------------------------------
  299. // TDatabaseDocument::DocumentNeedsSave
  300. //--------------------------------------------------------------------------------
  301. Boolean TDatabaseDocument::DocumentNeedsSave()
  302. {
  303.     Boolean documentIsDirty = false;
  304.     
  305.     //
  306.     // Test to see if any of the record groups need
  307.     // to be saved.
  308.     //
  309.     if(fRecordGroupList != nil)
  310.         for(long i=0; i<fNumberOfGroups; ++i)
  311.             if(fRecordGroupList[i] != nil)
  312.             {
  313.                 if(fRecordGroupList[i]->RecordGroupNeedsSave())
  314.                 {
  315.                     documentIsDirty = true;
  316.                     break;
  317.                 }
  318.             }
  319.     
  320.     return documentIsDirty;
  321. } // TDatabaseDocument::DocumentNeedsSave
  322.  
  323. //--------------------------------------------------------------------------------
  324. // TDatabaseDocument::MakeRecord
  325. //
  326. // This routine should be called directly (after GetFreeRecord returns the index
  327. // of a record to use) to create a new database record.  After the new record
  328. // is created, the creator should immediately set up the flags longword of the
  329. // new record.
  330. //
  331. // This routine is also called from TGroupControlObject::GetRecordCursor.
  332. //--------------------------------------------------------------------------------
  333. TAbstractRecord* TDatabaseDocument::MakeRecord(long recordIndex, long recordIDWord)
  334. {
  335.     TGroupControlObject* groupObject = this->GetGroupControlObject(recordIndex);
  336.     TAbstractRecord* cursor = nil;
  337.     
  338.     //
  339.     // First test:  if the high two bits are clear, this is a
  340.     // block-data record
  341.     //
  342.     if((recordIDWord & kBalanceFactorBits) == 0)
  343.     {
  344.         cursor = new TDataRecord(this, recordIndex);
  345.     }
  346.     else
  347.     {
  348.         //
  349.         // If the object record bit is clear, then this is an object record
  350.         //
  351.         if((recordIDWord & kObjectRecordDefinitionBit) == 0)
  352.         {
  353.             cursor = new TDBElement(this, recordIndex);
  354.         }
  355.         //
  356.         // If the property record bit is clear, then this is a property record
  357.         //
  358.         else if((recordIDWord & kDataRecordDefinitionBit) == 0)
  359.         {
  360.             cursor = new TDBProperty(this, recordIndex);
  361.         }
  362.         //
  363.         // Reserved for future expansion
  364.         //
  365.         else
  366.         {
  367.             FailErr(eDataCorrupt);
  368.         }
  369.     }
  370.     
  371.     //
  372.     // Cache the cursor if it was created
  373.     //
  374.     if(cursor != nil)
  375.         groupObject->CacheCreatedCursor(recordIndex, cursor);
  376.     
  377.     return cursor;
  378. } // TDatabaseDocument::MakeRecord
  379.  
  380. //--------------------------------------------------------------------------------
  381. // TDatabaseDocument::GetNextFreeIndex
  382. //--------------------------------------------------------------------------------
  383. long TDatabaseDocument::GetNextFreeIndex(long afterWhichFreeIndex) const
  384. {
  385.     TGroupControlObject* group = this->GetGroupControlObject(afterWhichFreeIndex);
  386.     return group->NextFreeIndex(afterWhichFreeIndex);
  387. } // TDatabaseDocument::GetNextFreeIndex
  388.  
  389. //--------------------------------------------------------------------------------
  390. // TDatabaseDocument::GetPreviousFreeIndex
  391. //--------------------------------------------------------------------------------
  392. long TDatabaseDocument::GetPreviousFreeIndex(long beforeWhichIndex) const
  393. {
  394.     TGroupControlObject* group = this->GetGroupControlObject(beforeWhichIndex);
  395.     long freeList = group->FreeListToUse(beforeWhichIndex);
  396.     //
  397.     // The first free list does not maintain a previous link.
  398.     // The previous link of the first item in any list may be invalid.
  399.     //
  400.     if((freeList == 0) || (this->GetFirstFreeIndex(freeList) == beforeWhichIndex))
  401.         return kNilIndex;
  402.     else
  403.         return group->PreviousFreeIndex(beforeWhichIndex);
  404. } // TDatabaseDocument::GetPreviousFreeIndex
  405.  
  406. //--------------------------------------------------------------------------------
  407. // TDatabaseDocument::PopIndexFromFreeList
  408. //
  409. // To make a new record, call this routine to get an index of a record that
  410. // isn't used, then call MakeRecordCursor directly, passing in the index and
  411. // the longword that identifies the type of record to create.
  412. //--------------------------------------------------------------------------------
  413. long TDatabaseDocument::PopIndexFromFreeList(long whichFreeList)
  414. {
  415.     //
  416.     // Get the first free index; if there are no free records
  417.     // left, then make some more.
  418.     //
  419.     long theFreeIndex = this->GetFirstFreeIndex(whichFreeList);
  420.     if(theFreeIndex != kNilIndex)
  421.     {
  422.         //
  423.         // Get the next free index in the linked list
  424.         //
  425.         long nextFreeIndex = this->GetNextFreeIndex(theFreeIndex);
  426.         this->SetFreeIndex(whichFreeList, nextFreeIndex);
  427.  
  428.         //
  429.         // Mark the free node as being unlinked from the free list
  430.         //
  431.         TGroupControlObject* group = this->GetGroupControlObject(theFreeIndex);
  432.         group->WriteRecordWord(theFreeIndex, kFreeNodeLinkByte, kFreeRecordNotLinkedToTree);
  433.     }
  434.     else
  435.     {
  436.         //
  437.         // If we make more free records, at least one of the
  438.         // new records is never pushed onto the free list
  439.         //
  440.         theFreeIndex = this->MakeMoreFreeRecords(whichFreeList);
  441.     }
  442.  
  443.     this->VerifyFreeLists();
  444.     
  445.     return theFreeIndex;
  446. } // TDatabaseDocument::PopIndexFromFreeList
  447.  
  448. //--------------------------------------------------------------------------------
  449. // TDatabaseDocument::PushFreeRecordOntoFreeList
  450. //--------------------------------------------------------------------------------
  451. void TDatabaseDocument::PushFreeRecordOntoFreeList(long recordToFree)
  452. {
  453.     TGroupControlObject* group = this->GetGroupControlObject(recordToFree);
  454.     long whichFreeList = group->FreeListToUse(recordToFree);
  455.     long recordToFreeAfterMerge = recordToFree;
  456.     
  457.     //
  458.     // Test to see if 'recordToFree' really is a free node.
  459.     // We don't enforce the lack of a cursor, though, as it may
  460.     // be that a cursor that is commiting or discarding changes
  461.     // has called this method to push the record back onto a free list.
  462.     //
  463.     Require(group->IndexIsFree(recordToFree));
  464.     
  465.     //
  466.     // Try to merge free blocks together
  467.     //
  468.     // We must ignore free blocks whose link word is kFreeRecordNotLinkedToTree,
  469.     // because these blocks are already claimed by a transaction.  Also, note
  470.     // that we adjust 'recordToFree' and 'whichFreeList' if any blocks are merged.
  471.     //
  472.     if(whichFreeList > 0)
  473.     {
  474.         long previousRecord = group->PreviousRecordIndex(recordToFree);
  475.         long nextRecord = group->NextRecordIndex(recordToFree);
  476.         
  477.         //
  478.         // If the previous record is free, merge it
  479.         //
  480.         if((previousRecord != kNilIndex) && group->IndexIsFreeAndOnFreeList(previousRecord))
  481.         {
  482.             whichFreeList = group->MergeFreeBlocks(previousRecord, recordToFree);    
  483.             recordToFreeAfterMerge = previousRecord;        
  484.             ASSERT(nextRecord == group->NextRecordIndex(recordToFreeAfterMerge));
  485.         }
  486.         
  487.         //
  488.         // If the next record is free, merge it
  489.         //
  490.         if((nextRecord != kNilIndex) && group->IndexIsFreeAndOnFreeList(nextRecord))
  491.         {
  492.             whichFreeList = group->MergeFreeBlocks(recordToFreeAfterMerge, nextRecord);    
  493.         }
  494.     }
  495.  
  496.     long nextFreeNode = this->GetFirstFreeIndex(whichFreeList);
  497.     group->WriteRecordWord(recordToFreeAfterMerge, kFreeNodeLinkByte, nextFreeNode);
  498.     this->SetFreeIndex(whichFreeList, recordToFreeAfterMerge);
  499.  
  500.     //
  501.     // Data records are stored in doubly-linked lists so they may
  502.     // be removed from their free list when they are merged with
  503.     // adjacent free blocks (DB records are never merged with
  504.     // any other record).
  505.     //
  506.     if(whichFreeList > 0)
  507.     {
  508.         //
  509.         // Note that although we clear the previous free node link
  510.         // of a record when we push it on the stack, we do not bother
  511.         // to clean up the previous free node link when objects are
  512.         // popped off the stack 
  513.         //
  514.         group->WriteRecordWord(recordToFreeAfterMerge, kPreviousFreeNodeLinkByte, kNilIndex);
  515.         if(nextFreeNode > kNilIndex)
  516.         {
  517.             //
  518.             // We know that any record in a free list does not belong
  519.             // to any transaction, so we blythly write to it
  520.             //
  521.             TGroupControlObject* groupOfNextFreeNode = this->GetGroupControlObject(nextFreeNode);
  522.             groupOfNextFreeNode->WriteRecordWord(nextFreeNode, kPreviousFreeNodeLinkByte, recordToFreeAfterMerge);
  523.         }
  524.     }
  525.     
  526.     //
  527.     // Any cached cursor to this node is now useless
  528.     //
  529.     group->RecordCursorStale(recordToFree);
  530.     this->VerifyFreeLists();
  531. } // TDatabaseDocument::PushFreeRecordOntoFreeList
  532.  
  533. //--------------------------------------------------------------------------------
  534. // TDatabaseDocument::RemoveFromFreeList
  535. //--------------------------------------------------------------------------------
  536. void TDatabaseDocument::RemoveFromFreeList(long recordToRemove)
  537. {
  538.     TGroupControlObject* group = this->GetGroupControlObject(recordToRemove);
  539.  
  540.     //
  541.     // If the record isn't currently on a free list, don't try to remove it again!
  542.     //
  543.     if(group->IndexIsFreeAndOnFreeList(recordToRemove) == true)
  544.     {
  545.         long freeList = group->FreeListToUse(recordToRemove);
  546.         
  547.         //
  548.         // The first free list is singly-linked; we do not support
  549.         // removing items from it.
  550.         //
  551.         Require(freeList > 0);
  552.     
  553.         long previousFreeIndex = this->GetPreviousFreeIndex(recordToRemove);
  554.         long nextFreeIndex = this->GetNextFreeIndex(recordToRemove);
  555.         
  556.         //
  557.         // If there is a next free index AND there is a previous free index,
  558.         // then fix up the previous link of the next index.  We don't bother
  559.         // to do this if there is no previous index because we always allow
  560.         // the previous link of the first item in the free list to be garbage.
  561.         //    
  562.         if((nextFreeIndex != kNilIndex) && (previousFreeIndex != kNilIndex))
  563.         {
  564.             TGroupControlObject* groupOfNextFreeNode = this->GetGroupControlObject(nextFreeIndex);
  565.             groupOfNextFreeNode->WriteRecordWord(nextFreeIndex, kPreviousFreeNodeLinkByte, previousFreeIndex);
  566.         }
  567.         
  568.         //
  569.         // If there is no previous link, then we need to fix up
  570.         // the head of the list pointer
  571.         //
  572.         if(previousFreeIndex == kNilIndex)
  573.         {
  574.             //
  575.             // We have blind faith that popping an item from the
  576.             // free list will do exactly what we want it to if
  577.             // the first item in the free list points to the
  578.             // record we'd like to remove
  579.             //
  580.             if(this->GetFirstFreeIndex(freeList) == recordToRemove)
  581.             {
  582.                 this->PopIndexFromFreeList(freeList);
  583.                 ASSERT(this->GetFirstFreeIndex(freeList) == nextFreeIndex);
  584.             }
  585.             else
  586.                 ASSERT(false);
  587.         }
  588.         else
  589.         {
  590.             TGroupControlObject* groupOfPreviousFreeNode = this->GetGroupControlObject(previousFreeIndex);
  591.             groupOfPreviousFreeNode->WriteRecordWord(previousFreeIndex, kFreeNodeLinkByte, nextFreeIndex);
  592.         }
  593.     }
  594.     
  595.     //
  596.     // Finally, mark this node as being free, but not linked to
  597.     // the tree
  598.     //
  599.     group->WriteRecordWord(recordToRemove, kFreeNodeLinkByte, kFreeRecordNotLinkedToTree);
  600. } // TDatabaseDocument::RemoveFromFreeList
  601.  
  602. //--------------------------------------------------------------------------------
  603. // TDatabaseDocument::VerifyFreeLists
  604. //--------------------------------------------------------------------------------
  605. void TDatabaseDocument::VerifyFreeLists() const
  606. {
  607.     for(long whichFreeList=0; whichFreeList<=kLastDataBlockFreeList; ++whichFreeList)
  608.     {
  609.         long freeIndex = this->GetFirstFreeIndex(whichFreeList);
  610.         long previousFreeIndex = kNilIndex;
  611.         while(freeIndex != kNilIndex)
  612.         {
  613.             long nextFreeIndex = kNilIndex;
  614.             
  615.             TGroupControlObject* group = this->GetGroupControlObject(freeIndex);
  616.             if(group->IndexIsFree(freeIndex) == false)
  617.                 DebugStr("\pNon-free record on the free list!");
  618.             if(group->FreeListToUse(freeIndex) != whichFreeList)
  619.                 DebugStr("\pFree record is wrong size for its free list!");
  620.             else
  621.             {
  622.                 //
  623.                 // All free lists but the first maintain previous
  624.                 // pointers for a doubly-linked list.  The first
  625.                 // item in the list may have a bogus previous link,
  626.                 // because we do not fix it up when we pop items.
  627.                 //
  628.                 if((whichFreeList > 0) && (previousFreeIndex != kNilIndex))
  629.                 {
  630.                     if(this->GetPreviousFreeIndex(freeIndex) != previousFreeIndex)
  631.                         DebugStr("\pPrevious free index link in free list doesn't point back");
  632.                 }
  633.                 
  634.                 nextFreeIndex = this->GetNextFreeIndex(freeIndex);
  635.             }
  636.  
  637.             previousFreeIndex = freeIndex;
  638.             freeIndex = nextFreeIndex;            
  639.         }
  640.     }
  641. } // TDatabaseDocument::VerifyFreeLists
  642.  
  643. //--------------------------------------------------------------------------------
  644. // TDatabaseDocument::NewDBProperty
  645. //--------------------------------------------------------------------------------
  646. AnUpdate<TDBProperty> TDatabaseDocument::NewDBProperty(TTransaction* transaction)
  647. {
  648.     AnUpdate<TDBProperty> dataUpdatePointer;
  649.     REQUIREVALIDPOINTER(transaction);
  650.     OSErr err = noErr;
  651.     
  652.     long freeIndex = this->PopIndexFromFreeList(kFreeRecordListIndex);
  653.     Try
  654.     {
  655.         AConst<TDBProperty> dataCursor(this->MakeRecord(freeIndex, kInitialDataRecordFlags)->DBPropertyCursor());
  656.         dataUpdatePointer = transaction->GetDBPropertyUpdatePointer(dataCursor);
  657.         dataUpdatePointer->InitializeNewRecord(transaction);
  658.     }
  659.     Catch(err)
  660.     {
  661.         //
  662.         // Push free index back onto a free list
  663.         //
  664.         Throw(err);
  665.     }
  666.  
  667.     return dataUpdatePointer;
  668. } // TDatabaseDocument::NewDBProperty
  669.  
  670. //--------------------------------------------------------------------------------
  671. // TDatabaseDocument::NewDBElement
  672. //--------------------------------------------------------------------------------
  673. AnUpdate<TDBElement> TDatabaseDocument::NewDBElement(TTransaction* transaction)
  674. {
  675.     AnUpdate<TDBElement> elementUpdatePtr;
  676.     REQUIREVALIDPOINTER(transaction);
  677.     OSErr err = noErr;
  678.     
  679.     long freeIndex = this->PopIndexFromFreeList(kFreeRecordListIndex);
  680.     Try
  681.     {
  682.         AConst<TDBElement> objectCursor(this->MakeRecord(freeIndex, kInitialObjectRecordFlags)->DBElementCursor());    
  683.         elementUpdatePtr = transaction->GetDBElementUpdatePointer(objectCursor);
  684.         elementUpdatePtr->InitializeNewRecord(transaction);
  685.     }
  686.     Catch(err)
  687.     {
  688.         //
  689.         // Push free index back onto a free list
  690.         //
  691.         Throw(err);
  692.     }
  693.  
  694.     return elementUpdatePtr;
  695. } // TDatabaseDocument::NewDBElement
  696.  
  697. //--------------------------------------------------------------------------------
  698. // TDatabaseDocument::NewDataRecord
  699. //--------------------------------------------------------------------------------
  700. AnUpdate<TDataRecord> TDatabaseDocument::NewDataRecord(TTransaction* transaction, long sizeOfData)
  701. {
  702.     AnUpdate<TDataRecord> dataUpdatePtr;
  703.     REQUIREVALIDPOINTER(transaction);
  704.     OSErr err = noErr;
  705.     
  706.     //
  707.     // Pick a free list that's the correct size for the amount of
  708.     // data requested.  Don't forget about the block header, either.
  709.     //
  710.     long sizeOfBlock = sizeOfData + kHeaderSize;
  711.     long whichFreeList = (sizeOfBlock - 1) / (kSingleRecordSize);
  712.     Require(whichFreeList <= kLastDataBlockFreeList);
  713.     long freeIndex = this->PopIndexFromFreeList(whichFreeList);
  714.     Try
  715.     {
  716.         AConst<TDataRecord> dataCursor(this->MakeRecord(freeIndex, kInitialDataBlockFlagsWord)->DataCursor());
  717.         dataUpdatePtr = transaction->GetDataRecordUpdatePointer(dataCursor);
  718.         dataUpdatePtr->InitializeNewDataRecord(transaction);
  719.     }
  720.     Catch(err)
  721.     {
  722.         //
  723.         // Push free index back onto a free list
  724.         //
  725.         Throw(err);
  726.     }
  727.     
  728.     return dataUpdatePtr;
  729. } // TDatabaseDocument::NewDataRecord
  730.  
  731. //--------------------------------------------------------------------------------
  732. // TDatabaseDocument::GetGroupControlObject
  733. //--------------------------------------------------------------------------------
  734. TGroupControlObject* TDatabaseDocument::GetGroupControlObject(long recordIndex) const
  735. {
  736.     long whichGroupControlObject = recordIndex / kRecordsPerGroup;
  737.     
  738.     if((whichGroupControlObject < 0) || (whichGroupControlObject >= fNumberOfGroups))
  739.         Throw(eIndexOutOfRange);
  740.     
  741.     //
  742.     // The group control object may be purged or unloaded,
  743.     // in which case fRecordGroupList[whichGroupControlObject]
  744.     // will be nil.  We need to test for this situation and
  745.     // recreate the group control object and reload its data
  746.     // from disk.
  747.     //
  748.     if(fRecordGroupList[whichGroupControlObject] == nil)
  749.     {
  750.         long firstRecord = whichGroupControlObject * kRecordsPerGroup;
  751.         ((TDatabaseDocument*)this)->CreateGroupControlObject(firstRecord);
  752.     }
  753.     Require(fRecordGroupList[whichGroupControlObject] != nil);
  754.  
  755.     return fRecordGroupList[whichGroupControlObject];
  756. } // TDatabaseDocument::GetGroupControlObject
  757.  
  758. //--------------------------------------------------------------------------------
  759. // TDatabaseDocument::InitializeNewGroup
  760. //--------------------------------------------------------------------------------
  761. long TDatabaseDocument::InitializeNewGroup(TGroupControlObject* group)
  762. {
  763.     REQUIREVALIDPOINTER(group);
  764.     long newFreeNode = kNilIndex;
  765.     
  766.     long newFirstFreeNode = group->InitializeNewGroup(this->GetFirstFreeIndex(kFreeRecordListIndex), newFreeNode);
  767.     if(newFirstFreeNode > kNilIndex)
  768.         this->SetFreeIndex(kFreeRecordListIndex, newFirstFreeNode);
  769.     
  770.     return newFreeNode;
  771. } // TDatabaseDocument::InitializeNewGroup
  772.  
  773. //--------------------------------------------------------------------------------
  774. // TDatabaseDocument::InitializeNewDataGroup
  775. //--------------------------------------------------------------------------------
  776. long TDatabaseDocument::InitializeNewDataGroup(TGroupControlObject* group, long desiredEncodedPhysicalSize)
  777. {
  778.     REQUIREVALIDPOINTER(group);
  779.     long leftOverBlock = kNilIndex;
  780.     
  781.     long newFreeNode = group->InitializeNewDataGroup(desiredEncodedPhysicalSize, leftOverBlock);
  782.     if(leftOverBlock > kNilIndex)
  783.         this->PushFreeRecordOntoFreeList(leftOverBlock);
  784.     
  785.     return newFreeNode;
  786. } // TDatabaseDocument::InitializeNewDataGroup
  787.  
  788. //--------------------------------------------------------------------------------
  789. // TDatabaseDocument::CreateGroupList
  790. //
  791. // Make a list of pointers to group control objects
  792. //--------------------------------------------------------------------------------
  793. void TDatabaseDocument::CreateGroupList(long numberOfGroups)
  794. {
  795.     if(numberOfGroups > fNumberOfGroups)
  796.     {
  797.         //
  798.         // Make a new list of group control objects,
  799.         // copy the old list to the new list and delete
  800.         // the old.
  801.         //
  802.         TGroupControlObject** newGroupList = new TGroupControlObject*[numberOfGroups];
  803.         FailNil(newGroupList);
  804.         
  805.         if(fRecordGroupList != nil)
  806.             CopyMemory(fRecordGroupList, newGroupList, fNumberOfGroups * sizeof(Ptr)); // memcpy(newGroupList, fRecordGroupList, fNumberOfGroups * sizeof(Ptr));
  807.             
  808.         delete [] fRecordGroupList;
  809.         fRecordGroupList = newGroupList;
  810.         
  811.         //
  812.         // Nil out the newly created pointers
  813.         //
  814.         while(numberOfGroups > fNumberOfGroups)
  815.         {
  816.             fRecordGroupList[fNumberOfGroups] = nil;
  817.             ++fNumberOfGroups;
  818.         }
  819.     }
  820. }
  821.  
  822. //--------------------------------------------------------------------------------
  823. // TDatabaseDocument::CacheGroupControlObject
  824. //--------------------------------------------------------------------------------
  825. void TDatabaseDocument::CacheGroupControlObject(TGroupControlObject* newGroup)
  826. {
  827.     REQUIREVALIDPOINTER(newGroup);
  828.     long newGroupNumber = newGroup->FirstRecordIndex() / kRecordsPerGroup;
  829.     OSErr err = noErr;
  830.     
  831.     //
  832.     // If there aren't enough pointers to group control
  833.     // objects, then make more.
  834.     //
  835.     this->CreateGroupList(newGroupNumber + 1);
  836.     fRecordGroupList[newGroupNumber] = newGroup;
  837. } // TDatabaseDocument::CacheGroupControlObject
  838.  
  839. //--------------------------------------------------------------------------------
  840. // TDatabaseDocument::CreateGroupControlObject
  841. //
  842. // Must not read any info from the disk; SetBackingStore depends on that.
  843. //--------------------------------------------------------------------------------
  844. TGroupControlObject* TDatabaseDocument::CreateGroupControlObject(long firstRecord)
  845. {
  846.     TGroupControlObject* newGroup = nil;    
  847.  
  848.     newGroup = new TGroupControlObject(this, firstRecord);
  849.     FailNil(newGroup);
  850.     this->CacheGroupControlObject(newGroup);
  851.     
  852.     return newGroup;
  853. } // TDatabaseDocument::CreateGroupControlObject
  854.  
  855. //--------------------------------------------------------------------------------
  856. // TDatabaseDocument::MakeMoreFreeRecords
  857. //--------------------------------------------------------------------------------
  858. long TDatabaseDocument::MakeMoreFreeRecords(long addToWhichFreeList)
  859. {
  860.     TGroupControlObject* newGroup = nil;
  861.     long newFreeNode = kNilIndex;
  862.     
  863.     //
  864.     // If we need more room in the free record list,
  865.     // then make a new set of records in a new record
  866.     // group control object.
  867.     //
  868.     long firstNewRecord = fNumberOfGroups * kRecordsPerGroup;
  869.     if(addToWhichFreeList == kFreeRecordListIndex)
  870.     {
  871.         newGroup = new TGroupControlObject(this, firstNewRecord);
  872.         this->CacheGroupControlObject(newGroup);
  873.         newFreeNode = this->InitializeNewGroup(newGroup);
  874.     }
  875.     else
  876.     {
  877.         //
  878.         // Never make the data record left over from the
  879.         // split only 1 block in size, because the rules
  880.         // are different for single-block records (their
  881.         // free list is singly-linked instead of doubly-linked)
  882.         //
  883.         long freeListToSplitFrom = addToWhichFreeList + 2;
  884.  
  885.         //
  886.         // Look for the next larger free list that has
  887.         // something in it
  888.         //
  889.         while(freeListToSplitFrom <= kLastDataBlockFreeList)
  890.         {
  891.             if(this->GetFirstFreeIndex(freeListToSplitFrom) != kNilIndex)
  892.                 break;
  893.             
  894.             ++freeListToSplitFrom;
  895.         }
  896.  
  897.         //
  898.         // If we went past the end of the list of free lists,
  899.         // then split from the last free list in the set
  900.         //
  901.         if(freeListToSplitFrom > kLastDataBlockFreeList)
  902.             freeListToSplitFrom = kLastDataBlockFreeList;
  903.  
  904.         //
  905.         // If the list we decided to split from is empty,
  906.         // then make a new group control object with
  907.         // another data block in it.
  908.         //
  909.         if(this->GetFirstFreeIndex(freeListToSplitFrom) == kNilIndex)
  910.         {
  911.             newGroup = new TGroupControlObject(this, firstNewRecord);
  912.             this->CacheGroupControlObject(newGroup);
  913.             newFreeNode = this->InitializeNewDataGroup(newGroup, addToWhichFreeList);
  914.         }
  915.         else
  916.         {
  917.             newFreeNode = this->PopIndexFromFreeList(freeListToSplitFrom);
  918.             TGroupControlObject* group = GetGroupControlObject(newFreeNode);
  919.             long leftOverBlock = group->TrimBlock(newFreeNode, addToWhichFreeList);
  920.             if(leftOverBlock > kNilIndex)
  921.                 this->PushFreeRecordOntoFreeList(leftOverBlock);
  922.         }
  923.     }
  924.     
  925.     return newFreeNode;
  926. } // TDatabaseDocument::MakeMoreFreeRecords
  927.  
  928. //--------------------------------------------------------------------------------
  929. // TDatabaseDocument::WriteDocumentInformation
  930. //--------------------------------------------------------------------------------
  931. void TDatabaseDocument::WriteDocumentInformation(TAbstractBackingStore* backingStoreToUse /*= nil*/)
  932. {
  933.     fDocumentInformation.fNumberOfGroupsOnDisk = fNumberOfGroups;
  934.     if(backingStoreToUse == nil)
  935.         backingStoreToUse = fBackingStore;
  936.  
  937.     if(backingStoreToUse != nil)
  938.     {
  939.         backingStoreToUse->Write(&this->fDocumentInformation, 0, sizeof(TDocumentFileInformation));
  940.         backingStoreToUse->Write(&this->fFreeList, this->fDocumentInformation.fStartOfFreeList, kSizeOfFreeList);
  941.     }
  942. }
  943.  
  944. //--------------------------------------------------------------------------------
  945. // TDatabaseDocument::ReadDocumentInformation
  946. //--------------------------------------------------------------------------------
  947. void TDatabaseDocument::ReadDocumentInformation()
  948. {
  949.     if(fBackingStore != nil)
  950.     {
  951.         //
  952.         // Trash the format identifier, just to make sure that
  953.         // 'Read' really is reading something.
  954.         //
  955.         this->fDocumentInformation.fFormatIdentifier = 'xxxx';
  956.         
  957.         //
  958.         // Read in the document header
  959.         //
  960.         fBackingStore->Read(&this->fDocumentInformation, 0, sizeof(TDocumentFileInformation));
  961.  
  962.         //
  963.         // Tests of logical consistancy:
  964.         //
  965.         // The format identifier must be correct
  966.         //
  967.         // The file format revision used to write this file must always be
  968.         // greater than or equal to the "compatable" versions; otherwise, the
  969.         // implication is that the file revision used to write this file
  970.         // cannot be read by the version of the app that wrote it.
  971.         //
  972.         // The earliest write-compatible revision must always be greater than
  973.         // or equal to the earliest compatible revision; otherwise the implication
  974.         // is that some version of this app can write a file format that it
  975.         // cannot read.
  976.         //
  977.         // Every valid saved document must have a meta-root.
  978.         //        
  979.         Require(this->fDocumentInformation.fFormatIdentifier == kFormatIdentifier);
  980.         Require(this->fDocumentInformation.fFileFormatRevisionNumber >= fDocumentInformation.fEarliestWriteCompatibleRevision);
  981.         Require(this->fDocumentInformation.fEarliestWriteCompatibleRevision >= fDocumentInformation.fEarliestCompatibleRevisionNumber);
  982.         Require(this->fDocumentInformation.fEarliestWriteCompatibleRevision >= fDocumentInformation.fEarliestCompatibleRevisionNumber);
  983.         Require(this->fDocumentInformation.fMetaRootID != kNilIndex);
  984.                 
  985.         //
  986.         // Can we understand this file format?
  987.         //
  988.         if(this->fDocumentInformation.fEarliestCompatibleRevisionNumber > kFileFormatRevisionNumber)
  989.             FailErr(-1); // •••Document is too new for us to read
  990.         if(this->fDocumentInformation.fFileFormatRevisionNumber < kEarliestInterpretedRevisionNumber)
  991.             FailErr(-1); // •••Document is too old for us to convert
  992.  
  993.         //
  994.         // If this document was written with a more recent format, and
  995.         // we are not allowed to write over said format, then mark the
  996.         // document as read-only.  If we are allowed to write over
  997.         // this revision, then immediately convert the file format revision
  998.         // numbers back down to what we will write out.
  999.         //
  1000.         if(this->fDocumentInformation.fFileFormatRevisionNumber > kFileFormatRevisionNumber)
  1001.         {
  1002.             //
  1003.             // The document will tell us if it wants us to write over anything it may have
  1004.             // written that we don't know about; usually, 'fEarliestWriteCompatibleRevision'
  1005.             // will equal the version that the document was written out to disk with.
  1006.             //
  1007.             if(this->fDocumentInformation.fEarliestWriteCompatibleRevision > kFileFormatRevisionNumber)
  1008.             {
  1009.                 fBackingStore->SetWriteEnable(false);
  1010.             }
  1011.             //
  1012.             // This branch should execute only rarely, if a new version of db+ introduces extensions
  1013.             // to the file format but does not mind if old versions of db+ ignore the extensions and
  1014.             // write garbage over them.  If we're going to be in a position to do that, then we'll
  1015.             // reset our document file format revision numbers so that the newer versions of db+
  1016.             // don't think that the extened information is still there.
  1017.             //
  1018.             else
  1019.             {                
  1020.                 this->fDocumentInformation.fFileFormatRevisionNumber = kFileFormatRevisionNumber;
  1021.                 this->fDocumentInformation.fEarliestCompatibleRevisionNumber = kEarliestCompatibleRevisionNumber;
  1022.                 this->fDocumentInformation.fEarliestWriteCompatibleRevision = kEarliestWriteCompatibleRevision;
  1023.                 this->fDocumentInformation.fNumberOfHeaderBytes = sizeof(TDocumentFileInformation);
  1024.             }
  1025.         }
  1026.         
  1027.         //
  1028.         // Read in the free list
  1029.         //
  1030.         fBackingStore->Read(&this->fFreeList, this->fDocumentInformation.fStartOfFreeList, kSizeOfFreeList);
  1031.     }
  1032.     this->CreateGroupList(fDocumentInformation.fNumberOfGroupsOnDisk);
  1033.     
  1034. #if 0
  1035.     //
  1036.     // If this file format is older than the current format, then
  1037.     // convert it into the current format.
  1038.     //
  1039.     if(this->fDocumentInformation.fFileFormatRevisionNumber < kFileFormatRevisionNumber)
  1040.     {
  1041.         //
  1042.         // If there are bits in the header that were meaningless in the old format,
  1043.         // then zero them out here.
  1044.         //
  1045.         if(this->fDocumentInformation.fNumberOfHeaderBytes < sizeof(TDocumentFileInformation))
  1046.         {
  1047.             long bytesToClear = sizeof(TDocumentFileInformation) - this->fDocumentInformation.fNumberOfHeaderBytes;
  1048.             char* bogusData = ((char*)&this->fDocumentInformation.fDocumentID) + this->fDocumentInformation.fNumberOfHeaderBytes;
  1049.             while(bytesToClear--)
  1050.                 *bogusData++ = 0;
  1051.         }
  1052.  
  1053.         //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  1054.         // BEGIN DOCUMENT CONVERSION CODE
  1055.         //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  1056.         
  1057.         //
  1058.         // Right now we only have one file format, but if in the future we need to
  1059.         // convert from one format to another, then the code to do
  1060.         //
  1061.         
  1062.         //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  1063.         // END DOCUMENT CONVERSION CODE
  1064.         //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  1065.         
  1066.         //
  1067.         // Once we have converted the format, write in updated version numbers
  1068.         //
  1069.         this->fDocumentInformation.fFileFormatRevisionNumber = kFileFormatRevisionNumber;
  1070.         this->fDocumentInformation.fEarliestCompatibleRevisionNumber = kEarliestCompatibleRevisionNumber;
  1071.         this->fDocumentInformation.fEarliestWriteCompatibleRevision = kEarliestWriteCompatibleRevision;
  1072.         this->fDocumentInformation.fNumberOfHeaderBytes = sizeof(TDocumentFileInformation);
  1073.     }
  1074. #endif
  1075. }
  1076.  
  1077. //--------------------------------------------------------------------------------
  1078. // TDatabaseDocument::CreateMetaRoot
  1079. //--------------------------------------------------------------------------------
  1080. void TDatabaseDocument::CreateMetaRoot()
  1081. {
  1082.     TTransaction transaction;
  1083.     
  1084.     AnUpdate<TDBElement> metaRoot = this->NewDBElement(&transaction);
  1085.     fDocumentInformation.fMetaRootID = metaRoot->RecordIndex();
  1086.     transaction.CommitChanges();
  1087. } // TDatabaseDocument::CreateMetaRoot
  1088.  
  1089. //--------------------------------------------------------------------------------
  1090. // TAbstractBackingStore::~TAbstractBackingStore
  1091. //--------------------------------------------------------------------------------
  1092. TAbstractBackingStore::~TAbstractBackingStore()
  1093. {
  1094. } // TAbstractBackingStore::~TAbstractBackingStore
  1095.  
  1096. //--------------------------------------------------------------------------------
  1097. // TAbstractBackingStore::CanSaveDocument
  1098. //--------------------------------------------------------------------------------
  1099. Boolean TAbstractBackingStore::CanSaveDocument()
  1100. {
  1101.     return fCanWrite;
  1102. }
  1103.  
  1104.